// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;

namespace Tierless.Infrastructure
{
	public static class FTPUtility
	{
		private static FtpWebRequest CreateFtpRequest(string AURL, string AUserName, string APassword, bool AEnableSSL, bool AUsePassive, string AMethod)
		{
			var LRequest = (FtpWebRequest)WebRequest.Create(AURL);
			if ((AUserName != null) && (APassword != null))
				LRequest.Credentials = new NetworkCredential(AUserName, APassword);
			LRequest.EnableSsl = AEnableSSL;
			LRequest.UsePassive = AUsePassive;
			LRequest.Method = AMethod;
			return LRequest;
		}
		
		private static FtpWebRequest CreateFtpRequest(string AURL, bool AEnableSSL, bool AUsePassive, string AMethod)
		{
			return CreateFtpRequest(AURL, null, null, AEnableSSL, AUsePassive, AMethod);
		}
		
		private static FtpWebRequest CreateFtpRequest(string AURL, string AUserName, string APassword, string AMethod)
		{
			return CreateFtpRequest(AURL, AUserName, APassword, false, false, AMethod);
		}
		
		private static FtpWebRequest CreateFtpRequest(string AURL, string AMethod)
		{
			return CreateFtpRequest(AURL, null, null, false, false, AMethod);
		}
		
		private static FtpWebResponse GetFtpResponse(FtpWebRequest ARequest)
		{
			return (FtpWebResponse)ARequest.GetResponse();
		}

		public static void Download(string AURL, string AUserName, string APassword, Stream ATargetStream)
		{
			var LResponse = GetFtpResponse(CreateFtpRequest(AURL, AUserName, APassword, WebRequestMethods.Ftp.DownloadFile));
			try
			{
				var LResponseStream = LResponse.GetResponseStream();
				try
				{
					StreamUtility.CopyStream(LResponseStream, ATargetStream);
				}
				finally
				{
					LResponseStream.Close();
				}
			}
			finally
			{
				LResponse.Close();
			}
		}
		
		public static void Download(string AURL, Stream ATargetStream)
		{
			Download(AURL, null, null, ATargetStream);
		}

		public static void Upload(string AURL, string AUserName, string APassword, bool AUsePassive, Stream ASourceStream)
		{
			var LRequest = CreateFtpRequest(AURL, AUserName, APassword, false, AUsePassive, WebRequestMethods.Ftp.UploadFile);
			var LRequestStream = LRequest.GetRequestStream();
			try
			{
				StreamUtility.CopyStream(ASourceStream, LRequestStream);
			}
			finally
			{
				LRequestStream.Close();
			}

			GetFtpResponse(LRequest).Close();
		}
		
		public static void Upload(string AURL, string AUserName, string APassword, Stream ASourceStream)
		{
			Upload(AURL, AUserName, APassword, false, ASourceStream);
		}
		
		public static void Upload(string AURL, Stream ASourceStream)
		{
			Upload(AURL, null, null, ASourceStream);
		}
		
		private static string GetListing(string AURL, string AUserName, string APassword)
		{
			try
			{
				var LResponse = GetFtpResponse(CreateFtpRequest(AURL, AUserName, APassword, WebRequestMethods.Ftp.ListDirectory));
				try
				{
					var LResponseStream = LResponse.GetResponseStream();
					try
					{
						var LReader = new StreamReader(LResponseStream);
						return LReader.ReadToEnd();
					}
					finally
					{
						LResponseStream.Close();
					}
				}
				finally
				{
					LResponse.Close();
				}
			}
			catch (WebException LWebException)
			{
				// HACK: This heinous hack is required because the API doesn't provide the actual
				// error number. For some stupid reason, the FTP site is returning an error
				// when attempting to get a listing from an empty directory, and the only way to
				// tell whether or not this is that error is to look for the string '(550)' in the
				// resulting error message.
				if (LWebException.Message.Contains("(550)"))
					return String.Empty;
				throw;
			}
		}
		
		public static List<String> List(string AURL, string AUserName, string APassword)
		{
			var LListing = GetListing(AURL, AUserName, APassword);
			var LResult = new List<String>();
			
			var LOffset = 0;
			while (LOffset < LListing.Length)
			{
				var LNextOffset = LListing.IndexOf('\n', LOffset + 1);
				if (LNextOffset < 0)
					LNextOffset = LListing.Length;
				LResult.Add(LListing.Substring(LOffset, LNextOffset - LOffset).Trim(new[] { '\r', '\n', ' ' }));
				LOffset = LNextOffset;
			}
			
			return LResult;
		}
		
		public static List<String> List(string AURL)
		{
			return List(AURL, null, null);
		}
		
		public static void Rename(string AURL, string AUserName, string APassword, string ANewName)
		{
			var LRequest = CreateFtpRequest(AURL, AUserName, APassword, WebRequestMethods.Ftp.Rename);
			LRequest.RenameTo = ANewName;
			GetFtpResponse(LRequest).Close();
		}
		
		public static void Rename(string AURL, string ANewName)
		{
			Rename(AURL, null, null, ANewName);
		}
		
		public static void Delete(string AURL, string AUserName, string APassword)
		{
			GetFtpResponse(CreateFtpRequest(AURL, AUserName, APassword, WebRequestMethods.Ftp.DeleteFile)).Close();
		}
		
		public static void Delete(string AURL)
		{
			Delete(AURL, null, null);
		}

		public static void MakeDirectory(string AURL, string AUserName, string APassword)
		{
			GetFtpResponse(CreateFtpRequest(AURL, AUserName, APassword, WebRequestMethods.Ftp.MakeDirectory)).Close();
		}
		
		public static void MakeDirectory(string AURL)
		{
			MakeDirectory(AURL, null, null);
		}

		public static void RemoveDirectory(string AURL, string AUserName, string APassword)
		{
			GetFtpResponse(CreateFtpRequest(AURL, AUserName, APassword, WebRequestMethods.Ftp.RemoveDirectory)).Close();
		}
		
		public static void RemoveDirectory(string AURL)
		{
			RemoveDirectory(AURL, null, null);
		}
	}
}
